package com.jzo2o.market.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.RedisConstants;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
import static com.jzo2o.market.enums.ActivityStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {

    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        //活动信息
        Object activityListJson = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtils.isNull(activityListJson)) {
            return null;
        }
        //将json转成list
        List<ActivityInfoResDTO> activityInfoResDTOS = JsonUtils.toList(activityListJson.toString(), ActivityInfoResDTO.class);

        //从activityInfoResDTOS找到该活动
        return activityInfoResDTOS.stream().filter(v -> v.getId().equals(id)).findFirst().orElse(null);
    }

    @Override
    public void deductStock(Long id) {
        // update activity t set t.stock_num = t.stock_num - 1 where t.id = ? and t.stock_num > 0
        boolean update = this.lambdaUpdate()
                .setSql("stock_num = stock_num - 1")
                .eq(Activity::getId, id)
                .gt(Activity::getStockNum, 0)
                .update();

        if (!update) {
            throw new CommonException("修改活动" + id + "库存失败");
        }

    }

    /**
     * 预热活动列表和库存
     */
    @Override
    public void preHeat() {

        /**
         select *
         from activity t
         where t.distribute_start_time <= date_add(now(), interval 30 day)
         and t.status in (1, 2)
         order by t.distribute_start_time asc
         */

        // 查询进行中还未到结束的优惠券活动， 1个月内待开始的优惠券活动
        List<Activity> list = this.lambdaQuery()
                .le(Activity::getDistributeStartTime, LocalDateTime.now().plusMonths(1))
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))
                .orderByAsc(Activity::getDistributeStartTime)
                .list();
        if (CollUtils.isEmpty(list)) {
            // 防止缓存穿透
            list = Collections.emptyList();
        }
        //将List<Activity>转List<SeizeCouponInfoResDTO>
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtils.copyToList(list, SeizeCouponInfoResDTO.class);
        //将SeizeCouponInfoResDTO转成json
        String seizeCouponInfoResString = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);
        //将查询到的活动存入redis
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST, seizeCouponInfoResString);

        //对未开始的活动的库存直接更新到redis
        list.stream().filter(v -> NO_DISTRIBUTE.getStatus() == getStatus(v.getDistributeStartTime(), v.getDistributeEndTime(), v.getStatus())).forEach(v -> {
            String key = String.format("COUPON:RESOURCE:STOCK:{%s}", v.getId() % 10);
            redisTemplate.opsForHash().put(key, v.getId(), v.getTotalNum());
        });

        //对已经开始的活动库存,如果redis未设置库存才进行设置
        list.stream().filter(v -> DISTRIBUTING.getStatus() == getStatus(v.getDistributeStartTime(), v.getDistributeEndTime(), v.getStatus())).forEach(v -> {
            String key = String.format("COUPON:RESOURCE:STOCK:{%s}", v.getId() % 10);
            //putIfAbsent如果未设置才进行设置
            redisTemplate.opsForHash().putIfAbsent(key, v.getId(), v.getTotalNum());
        });

    }

    /**
     * 查询活动列表
     * @param tabType 1：疯抢中，2：即将开始
     * @return
     */
    @Override
    public List<SeizeCouponInfoResDTO> queryForListFromCache(Integer tabType) {
        //查询redis中的活动列表 ACTIVITY:LIST (json串)
        Object seizeCouponInfoStr = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);

        if (ObjectUtils.isNull(seizeCouponInfoStr)) {
            return Collections.emptyList();
        }

        //确定要查询的状态(对应数据库的状态）前端传入进行中的状态为1，对应到数据库的活动状态为2，前端传入的是2对应到数据库的活动状态为1
        int queryStatus = tabType == 1 ? DISTRIBUTING.getStatus() : NO_DISTRIBUTE.getStatus();

        //将json转成List<SeizeCouponInfoResDTO>
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = JsonUtils.toList(JSONUtil.toJsonStr(seizeCouponInfoStr), SeizeCouponInfoResDTO.class);

        //根据tabType过滤活动列表，在过滤过程中根据活动的开始时间、结束时间及当前时间判断出准确状态，将这个准确的状态写入SeizeCouponInfoResDTO对象
        //stream流的peek是中间过程方法

        return seizeCouponInfoResDTOS.stream().filter(v -> queryStatus == getStatus(v.getDistributeStartTime(), v.getDistributeEndTime(), v.getStatus()))
                .peek(v -> {
                    //库存
                    v.setRemainNum(v.getStockNum());
                    v.setStatus(queryStatus);
                }).collect(Collectors.toList());


    }

    //根据活动的开始时间、结束时间、数据库中的状态 判断出的准确的状态
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        //当前时间
        LocalDateTime now = LocalDateTime.now();
        if (status == NO_DISTRIBUTE.getStatus()
                && distributeStartTime.isBefore(now)
                && distributeEndTime.isAfter(now)) {  //如果当前状态是待生效，如果开始时间小于当前时间 并且结束时间大于当前时间 活动已经开始了，返回的状态是进行中
            return DISTRIBUTING.getStatus();
        } else if (status == NO_DISTRIBUTE.getStatus()
                && distributeEndTime.isBefore(now)) {//如果当前状态是待生效，如果结束时间小于当前时间，活动已经结束了，返回的状态是已结束
            return LOSE_EFFICACY.getStatus();
        } else if (status == DISTRIBUTING.getStatus()
                && distributeEndTime.isBefore(now)) { //如果当前状态是进行中，如果结束时间小于当前时间，活动已经结束了，返回的状态是已结束
            return LOSE_EFFICACY.getStatus();
        }
        return status;
    }

    @Override
    public PageResult<ActivityInfoResDTO> queryForPage(ActivityQueryForPageReqDTO activityQueryForPageReqDTO) {
        // 1.查询准备
        LambdaQueryWrapper<Activity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper.eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getId()), Activity::getId, activityQueryForPageReqDTO.getId())
                .like(StringUtils.isNotEmpty(activityQueryForPageReqDTO.getName()), Activity::getName, activityQueryForPageReqDTO.getName())
                .eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getType()), Activity::getType, activityQueryForPageReqDTO.getType())
                .eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getStatus()), Activity::getStatus, activityQueryForPageReqDTO.getStatus());

        // 排序
        lambdaQueryWrapper.orderByDesc(Activity::getId);
        // 分页
        Page<Activity> activityPage = new Page<>(activityQueryForPageReqDTO.getPageNo().intValue(), activityQueryForPageReqDTO.getPageSize().intValue());
        activityPage = baseMapper.selectPage(activityPage, lambdaQueryWrapper);
        return PageUtils.toPage(activityPage, ActivityInfoResDTO.class);
    }

    @Override
    public ActivityInfoResDTO queryById(Long id) {
        // 1.获取活动
        Activity activity = baseMapper.selectById(id);
        // 判空
        if (activity == null) {
            return new ActivityInfoResDTO();
        }
        // 2.数据转换，并返回信息
        ActivityInfoResDTO activityInfoResDTO = BeanUtils.toBean(activity, ActivityInfoResDTO.class);
        // 设置状态
//        activityInfoResDTO.setStatus(getStatus(activity.getDistributeStartTime(), activity.getDistributeEndTime(), activity.getStatus()));
        // 3.领取数量
//        Integer receiveNum = couponService.countReceiveNumByActivityId(activity.getId());
        Integer receiveNum = activity.getTotalNum() - activity.getStockNum();
        activityInfoResDTO.setReceiveNum(receiveNum);
        // 4.核销量
        Integer writeOffNum = couponWriteOffService.countByActivityId(id);
        activityInfoResDTO.setWriteOffNum(NumberUtils.null2Zero(writeOffNum));

        //
        return activityInfoResDTO;
    }

    @Override
    @Transactional
    public void save(ActivitySaveReqDTO activitySaveReqDTO) {
        // 1.逻辑校验
        activitySaveReqDTO.check();
        // 2.活动数据组装
        // 转换
        Activity activity = BeanUtils.toBean(activitySaveReqDTO, Activity.class);
        // 状态
        activity.setStatus(NO_DISTRIBUTE.getStatus());
        //库存
        activity.setStockNum(activitySaveReqDTO.getTotalNum());

        // 3.保存
        saveOrUpdate(activity);
    }


    @Override
    public void updateStatus() {
        LocalDateTime now = DateUtils.now();
        // 1.更新已经进行中的状态
        lambdaUpdate()
                .set(Activity::getStatus, ActivityStatusEnum.DISTRIBUTING.getStatus())//更新活动状态为进行中
                .eq(Activity::getStatus, NO_DISTRIBUTE)//检索待生效的活动
                .le(Activity::getDistributeStartTime, now)//活动开始时间小于等于当前时间
                .gt(Activity::getDistributeEndTime, now)//活动结束时间大于当前时间
                .update();
        // 2.更新已经结束的
        lambdaUpdate()
                .set(Activity::getStatus, LOSE_EFFICACY.getStatus())//更新活动状态为已失效
                .in(Activity::getStatus, Arrays.asList(DISTRIBUTING.getStatus(), NO_DISTRIBUTE.getStatus()))//检索待生效及进行中的活动
                .lt(Activity::getDistributeEndTime, now)//活动结束时间小于当前时间
                .update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long id) {
        // 1.活动作废
        boolean update = lambdaUpdate()
                .set(Activity::getStatus, ActivityStatusEnum.VOIDED.getStatus())
                .eq(Activity::getId, id)
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))
                .update();
        if (!update) {
            return;
        }
        // 2.未使用优惠券作废
        couponService.revoke(id);

    }

}
